//
// Created by song on 16-11-18.
//

#ifndef C0COMPILER_SYMBOLTABLE_H
#define C0COMPILER_SYMBOLTABLE_H


#include <vector>
#include <sstream>
#include "Token.h"
#include "../helper/Error.h"
#include "../back/IR.h"

class SymbolTable{
public:
    enum Type{
        CHAR, INT, VOID
    };
    class VarRecord{
    public:
        int id;
        bool isArray;
        bool isConst;
        Type type;
        int size;
        int offset;
        string name;
        int value; // used when isConst;
        TmpVar* ref;
        bool isGlobal;
        VarRecord(){
            id = -1;
            isArray = false;
            isConst = false;
            type = VOID;
            size = -1;
            offset = -1;
            value = -1;
        }
        string toString(){
            stringstream result;
            result << id << '\t' << offset << '\t';
            switch(type){
                case CHAR: result << "char"; break;
                case INT:  result << "int ";  break;
                default: Error::internal(Error::Should_Not_Happen);
            }
            long blankLength = 12 - name.size();
            if(blankLength>0){
                string blank(blankLength, ' ');
                result << blank;
            }
            result << name << "\t";
            if(isConst){
                result << "const";
            }else{
                result << "-----";
            }
            result << '\t';
            if(isArray){
                result << "array";
            } else{
                result << "-----";
            }
            result << '\t';
            if(isGlobal){
                result << "global";
            } else{
                result << "------";
            }
            result << '\t';
            result << size;
            result << '\t';
            result << value;
            return result.str();
        }
    };
    class FunRecord{
    public:
        int id;
        Type returnType;
        int argCount;
        int offset;
        string name;
        Type argSignature[32];
        TmpVar *ref;

        FunRecord(){
            id = -1;
            returnType = VOID;
            argCount = -1;
            offset = -1;
        }
        string toString(){
            stringstream result;
            result << id << '\t' << offset << '\t';
            switch(returnType){
                case CHAR: result << "char"; break;
                case INT:  result << "int ";  break;
                case VOID: result << "void"; break;
            }
            long blankLength = 12 - name.size();
            if(blankLength>0){
                string blank(blankLength, ' ');
                result << blank;
            }
            result << name << "\t" << argCount << "\t( ";
            for(int i=0;i<argCount;i++){
                if(argSignature[i]==INT){
                    result << "int ";
                }else{
                    result << "char";
                }
                if(i<argCount-1) result << " ";
            }
            result << ")";
            return result.str();
        }

    };
    vector<VarRecord*> varTable;
    vector<FunRecord*> funTable;

private:
    int globalVarEndId;
    int curScopeStartId;
    bool insideFunction;

    map<int, VarRecord*> varMap;
    map<int, FunRecord*> funMap;

    int nextID;

    int getNewID(){
        int tmp = nextID;
        nextID++;
        return tmp;
    }

    int enter(VarRecord *r) {
        varTable.push_back(r);
        int id = getNewID();
        r->id = id;
        varMap[id] = r;
        return id;
    }

    int enterFunction(FunRecord *r) {
        funTable.push_back(r);
        int id = getNewID();
        r->id = id;
        funMap[id] = r;
        return id;
    }

public:
    SymbolTable(){
        this->nextID = 0;
        insideFunction = false;
        globalVarEndId = -1;
    }

    int enterConst(string name, TokenType type, int value) {
        VarRecord* r = new VarRecord();
        r->isArray = false;
        r->isConst = true;
        r->name = name;
        r->value = value;
        if(type==Char){
            r->type = CHAR;
        }else{
            r->type = INT;
        }
        if(insideFunction){
            r->isGlobal = false;
        }else{
            r->isGlobal = true;
        }
        return enter(r);
    }

    int enterVar(string name, TokenType type){
        VarRecord* r = new VarRecord();
        r->isArray = false;
        r->isConst = false;
        r->name = name;
        if(type==Char){
            r->type = CHAR;
        }else{
            r->type = INT;
        }
        if(insideFunction){
            r->isGlobal = false;
        }else{
            r->isGlobal = true;
        }
        return enter(r);
    }

    int enterArray(string name, TokenType type, int size){
        VarRecord* r = new VarRecord();
        r->isArray = true;
        r->isConst = false;
        r->size = size;
        r->name = name;
        if(type==Char){
            r->type = CHAR;
        }else{
            r->type = INT;
        }
        if(insideFunction){
            r->isGlobal = false;
        }else{
            r->isGlobal = true;
        }
        return enter(r);
    }



    int enterFunction(string name, TokenType type, vector<TokenType> argSig){
        FunRecord* r = new FunRecord();
        if(argSig.size()>32){
            Error::semantic(Error::Exceeded_Max_Argument_Size);
        }else{
            r->argCount = (int) argSig.size();
        }
        r->name = name;
        if(type==Char){
            r->returnType = CHAR;
        }else if(type==Int){
            r->returnType = INT;
        }else{
            r->returnType = VOID;
        }
        for(int i=0;i<argSig.size(); i++){
            Type t;
            if(argSig[i]==Char){
                t = CHAR;
            }else if(argSig[i]==Int) {
                t = INT;
            }else {
                Error::semantic(Error::Should_Not_Happen);
            }
            r->argSignature[i] = t;
        }
        return enterFunction(r);
    }

    void goIntoFunction(){// this one is call after push function. before push param.
        if(globalVarEndId<0){// run only once, update global var end.
            globalVarEndId = nextID-1;
        }
        curScopeStartId = nextID;
        insideFunction = true;
    }

    void getOutFunction(){
        insideFunction = false;
    }

    int lookUpVar(string name, bool inCurrentScope) {
        if(insideFunction){
            for(long i=varTable.size()-1;i>=0;i--){
                VarRecord* v = varTable[i];
                if(!inCurrentScope){
                    if(v->id >= curScopeStartId || globalVarEndId >= v->id ){
                        if(v->name==name){
                            return v->id;
                        }
                    }
                }else{
                    if(v->id >= curScopeStartId){
                        if(v->name==name){
                            return v->id;
                        }
                    }
                }
            }
        }else{
            for(long i=varTable.size()-1;i>=0;i--){
                VarRecord* v = varTable[i];
                if(v->name==name){
                    return v->id;
                }
            }
        }
        return -1;
    }

    int lookUpFun(string name) {
        for(long i=funTable.size()-1;i>=0;i--){
            FunRecord* v = funTable[i];
            if(v->name==name){
                return v->id;
            }
        }
        return -1;
    }

    FunRecord* getFun(int id) {
        if(funMap.count(id)>0){
            return funMap[id];
        }else{
            Error::nextErrorDetail << "id(" << id << ") not in FunTable";
            Error::internal(Error::Should_Not_Happen);
        }
    }

    VarRecord* getVar(int id){
        if(varMap.count(id)>0){
            return varMap[id];
        }else{
            Error::nextErrorDetail << "id(" << id << ") not in VarTable";
            Error::internal(Error::Should_Not_Happen);
        }
    }

    void print() {
        cout << "FUNCTION TABLE -----------------------------------------"<<endl;
        vector<FunRecord*>::const_iterator fIter;
        for(fIter=funTable.begin(); fIter!=funTable.end(); fIter++){
            FunRecord* v = *fIter;
            cout << v->toString() << endl;
        }
        cout << "VARIABLE TABLE -----------------------------------------"<<endl;
        vector<VarRecord*>::const_iterator vIter;
        for(vIter=varTable.begin(); vIter!=varTable.end(); vIter++){
            VarRecord* v = *vIter;
            cout << v->toString() << endl;
        }
    }

    virtual ~SymbolTable() {
//        cout << "destroy me"<<endl;
    }

};

#endif //C0COMPILER_SYMBOLTABLE_H
